package com.smedi.ismedi.inmail.core.application.service.impl;

import com.smedi.ismedi.authority.starter.util.AuthorityUtil;
import com.smedi.ismedi.authority.starter.util.VerifyHelper;
import com.smedi.ismedi.file.dto.response.FileInfoResponse;
import com.smedi.ismedi.inmail.core.application.command.*;
import com.smedi.ismedi.inmail.core.application.convert.AppConverter;
import com.smedi.ismedi.inmail.core.application.service.ProcessApplicationService;
import com.smedi.ismedi.inmail.core.domain.aggregatemodel.*;
import com.smedi.ismedi.inmail.core.domain.dto.*;
import com.smedi.ismedi.inmail.core.domain.repository.ProcessAttachmentRepository;
import com.smedi.ismedi.inmail.core.domain.repository.ProcessCommentRepository;
import com.smedi.ismedi.inmail.core.domain.repository.ProcessVariableRepository;
import com.smedi.ismedi.inmail.core.infrastructure.constant.ConstantField;
import com.smedi.ismedi.inmail.core.infrastructure.constant.ProcessError;
import com.smedi.ismedi.inmail.core.port.adapter.service.FileRemoteApi;
import com.smedi.ismedi.inmail.core.port.adapter.service.OrganizationRemoteApiService;
import com.smedi.ismedi.inmail.core.port.adapter.service.PersonRemoteApi;
import com.smedi.ismedi.organization.api.dto.response.resp.OrganizationResp;
import com.smedi.ismedi.personnel.api.dto.response.resp.OrganizationAssignmentResp;
import com.smedi.ismedi.personnel.api.dto.response.resp.PersonResp;
import com.smedi.ismedi.web.starter.exception.LogicException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceModificationBuilder;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.ExtensionElements;
import org.camunda.bpm.model.bpmn.instance.Task;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperties;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;

import static com.smedi.ismedi.authority.starter.common.Constant.DEFAULT_STRING;
import static com.smedi.ismedi.inmail.core.infrastructure.constant.ConstantField.CANCEL;
import static com.smedi.ismedi.inmail.core.infrastructure.constant.ConstantField.METHOD;
import static com.smedi.ismedi.web.starter.thread.ThreadPool.IO_INSTANCE;

/**
 * 通知公告应用服务实现类
 *
 * @author jiangqinxuan
 * @date 2021/07/26
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class ProcessApplicationServiceImpl implements ProcessApplicationService {

    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final HistoryService historyService;
    private final AppConverter appConverter;
    private final AuthorityUtil authorityUtil;
    private final PersonRemoteApi personRemoteApi;
    private final ProcessAttachmentRepository processAttachmentRepository;
    private final ProcessCommentRepository processCommentRepository;
    private final ProcessVariableRepository processVariableRepository;
    private final OrganizationRemoteApiService organizationRemoteApiService;
    private final FileRemoteApi fileRemoteApi;

    @Override
    public void deployProcessDefinition(MultipartFile processPerformFile) throws IOException {
        String name = Objects.requireNonNull(StringUtils.split(processPerformFile.getOriginalFilename(), "."))[0];
        repositoryService.createDeployment()
                .name(name)
                .addInputStream(processPerformFile.getResource().getFilename(), processPerformFile.getInputStream())
                .deploy();
    }

    @Override
    public List<ProcessTask> getProcessTaskList(ProcessTaskListCommand processTaskListCommand) {
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(processTaskListCommand.getBusinessKey())
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .asc()
                .list();

        List<HistoricTaskInstance> taskCurrents = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(processTaskListCommand.getBusinessKey())
                .unfinished()
                .list();
        if (VerifyHelper.isAvailable(taskCurrents)) {
            historicTaskInstances.addAll(taskCurrents);
        }

        return this.extend(historicTaskInstances);
    }

    @Override
    public ProcessTask startProcessInstance(ProcessInstanceStartCommand processInstanceStartCommand) {
        String businessKey = processInstanceStartCommand.getBusinessKey();

        runtimeService.startProcessInstanceByKey(processInstanceStartCommand.getProcessDefinitionKey(), businessKey, processInstanceStartCommand.getVariables());

        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .list();

        if (historicTaskInstances.size() > 1) {
            throw LogicException.raise(ProcessError.PROCESS_NOT_END);
        }

        return this.extend(historicTaskInstances.get(0));
    }

    @Override
    public List<ProcessTask> completeProcessTask(ProcessTaskCompleteCommand processTaskCompleteCommand) {
        HistoricTaskInstance taskLast = null;
        String businessKey = processTaskCompleteCommand.getBusinessKey();
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .singleResult();

        List<HistoricTaskInstance> tasksLast = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .list();

        if (!VerifyHelper.isAvailable(tasksLast)) {
            throw LogicException.raise(ProcessError.PROCESS_IS_END);
        }

        // 串行网关
        if (tasksLast.size() == 1) {
            taskLast = tasksLast.get(0);
        }
        // 并行网关
        if (tasksLast.size() > 1) {
            taskLast = tasksLast.stream().filter(task -> task.getTaskDefinitionKey().equals(processTaskCompleteCommand.getTaskDefinitionKey())).findFirst().orElse(null);
        }

        if (!VerifyHelper.isAvailable(taskLast)) {
            throw LogicException.raise(ProcessError.TASK_NOT_FOUND);
        }

        String lastTaskId = taskLast.getId();

        taskService.setOwner(lastTaskId, String.valueOf(authorityUtil.getPersonId()));

        if (null != processTaskCompleteCommand.getVariables()) {
            for (Map.Entry<String, Object> entry : processTaskCompleteCommand.getVariables().entrySet()) {
                taskService.setVariableLocal(lastTaskId, entry.getKey(), entry.getValue());
            }
        }

        if (null != processTaskCompleteCommand.getComment()) {
            taskService.createComment(lastTaskId, processInstance.getId(), processTaskCompleteCommand.getComment());
        }

        if (VerifyHelper.isAvailable(processTaskCompleteCommand.getFileKeys())) {
            for (String fileKey : processTaskCompleteCommand.getFileKeys()) {
                taskService.createAttachment(null, lastTaskId, null, null, fileKey, DEFAULT_STRING);
            }
        }

        taskService.complete(lastTaskId, processTaskCompleteCommand.getVariables());

        List<HistoricTaskInstance> taskCurrents = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .list();

        if (!VerifyHelper.isAvailable(taskCurrents)) {
            return null;
        }

        List<ProcessTask> processTasks = this.extend(taskCurrents);
        for (ProcessTask processTask : processTasks) {
            BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId());
            Task task = bpmnModelInstance.getModelElementById(processTask.getTaskDefinitionKey());
            ExtensionElements extensionElements = task.getExtensionElements();
            Collection<CamundaProperty> camundaProperties = extensionElements.getElementsQuery()
                    .filterByType(CamundaProperties.class)
                    .singleResult()
                    .getCamundaProperties();

            for (CamundaProperty camundaProperty : camundaProperties) {
                processTask.setStatus(camundaProperty.getCamundaValue());
            }
        }

        // 暂时串行时才能指派委托人
        if (taskCurrents.size() == 1) {
            if (null != processTaskCompleteCommand.getNextAssignee()) {
                taskService.setAssignee(taskCurrents.get(0).getId(), String.valueOf(processTaskCompleteCommand.getNextAssignee()));
            }
        }

        return processTasks;
    }

    @Override
    public void closeProcessInstance(ProcessInstanceCloseCommand processInstanceCloseCommand) {
        String businessKey = processInstanceCloseCommand.getBusinessKey();
        HistoricTaskInstance taskCurrent = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .singleResult();
        if (null != processInstanceCloseCommand.getComment()) {
            taskService.createComment(taskCurrent.getId(), taskCurrent.getProcessInstanceId(), processInstanceCloseCommand.getComment());
        }
        runtimeService.deleteProcessInstance(taskCurrent.getRootProcessInstanceId(), null);
    }

    @Override
    public List<ProcessTask> cancelProcessTask(ProcessTaskCancelCommand processTaskCancelCommand) {
        String businessKey = processTaskCancelCommand.getBusinessKey();

        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceBusinessKey(processTaskCancelCommand.getBusinessKey())
                .active()
                .singleResult();

        List<HistoricTaskInstance> tasksCurrent = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .list();

        HistoricTaskInstance taskLast = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .list()
                .get(0);

        ProcessInstanceModificationBuilder processInstanceModification = runtimeService.createProcessInstanceModification(processInstance.getProcessInstanceId());

        // 1.如果上一个任务和当前任务相同taskId 则属于并行网关完成部分，不撤销上一个任务 todo  这个部分有问题  暂时不支持并行网关支路内部的撤回  后续考虑配置在流程图中读取
        if(taskLast.getId().equals(tasksCurrent.get(0).getId())){
            processInstanceModification = processInstanceModification.cancelActivityInstance(taskLast.getActivityInstanceId());
        }
        // 2.如果上一个任务和当前任务不同taskId 则为正常串行，撤销全部未完成的任务
        if(!taskLast.getId().equals(tasksCurrent.get(0).getId())) {
            for (HistoricTaskInstance taskCurrent : tasksCurrent) {
                taskService.setVariableLocal(taskCurrent.getId(), METHOD, CANCEL);
                if (null != processTaskCancelCommand.getComment()) {
                    taskService.createComment(taskCurrent.getId(), taskCurrent.getProcessInstanceId(), processTaskCancelCommand.getComment());
                }
                taskService.setOwner(taskCurrent.getId(), String.valueOf(authorityUtil.getPersonId()));
                processInstanceModification = processInstanceModification.cancelActivityInstance(taskCurrent.getActivityInstanceId());
            }
        }

        processInstanceModification.startBeforeActivity(taskLast.getTaskDefinitionKey())
                .execute();

        return this.extend(historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .unfinished()
                .list());
    }

    private ProcessTask extend(HistoricTaskInstance historicTaskInstance) {
        if (null == historicTaskInstance) {
            return null;
        }

        List<ProcessTask> processTasks = this.extend(new ArrayList<>(Collections.singletonList(historicTaskInstance)));

        return processTasks.get(0);
    }

    private List<ProcessTask> extend(List<HistoricTaskInstance> historicTaskInstances) {
        List<ProcessTask> processTasks = new ArrayList<>();
        if (historicTaskInstances.isEmpty()) {
            return processTasks;
        }

        Set<Long> personIds = new HashSet<>();
        Set<String> ids = new HashSet<>();
        Set<Long> orgIds = new HashSet<>();
        Set<String> fileKeys = new HashSet<>();
        Map<Long, Person> idPersonMap = new HashMap<>();
        Map<Long, Long> personIdOrgIdMap = new HashMap<>();
        Map<String, List<ProcessAttachment>> taskIdProcessAttachmentsMap = new HashMap<>();
        Map<String, com.smedi.ismedi.inmail.core.domain.aggregatemodel.Attachment> keyFileMap = new HashMap<>();
        Map<String, List<ProcessComment>> taskIdProcessCommentsMap = new HashMap<>();
        Map<String, List<ProcessVariable>> taskIdProcessVariablesMap = new HashMap<>();
        Map<Long, Organization> idOrgMap = new HashMap<>();

        for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
            String ownerIdString = historicTaskInstance.getOwner();
            if (StringUtils.isNotBlank(ownerIdString)) {
                personIds.add(Long.valueOf(ownerIdString));
            }
            ids.add(historicTaskInstance.getId());
        }

        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(personIds)) {
                        List<PersonResp> people = personRemoteApi.getPersonList(PersonQuery
                                .builder()
                                .ids(new ArrayList<>(personIds))
                                .build());
                        for (PersonResp person : people) {
                            idPersonMap.put(person.getId(), appConverter.personRespToPerson(person));
                        }
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(personIds)) {
                        List<OrganizationAssignmentResp> organizationAssignments = personRemoteApi.getOrganizationAssignmentList(OrganizationAssignmentQuery
                                .builder().personIds(new ArrayList<>(personIds)).build());
                        for (OrganizationAssignmentResp organizationAssignment : organizationAssignments) {
                            orgIds.add(organizationAssignment.getHrOrganizationId());
                            personIdOrgIdMap.put(organizationAssignment.getPersonId(), organizationAssignment.getHrOrganizationId());
                        }
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(ids)) {
                        List<ProcessAttachment> processAttachments = processAttachmentRepository.getProcessAttachmentList(ProcessAttachmentQuery
                                .builder()
                                .taskIds(new ArrayList<>(ids))
                                .build());
                        for (ProcessAttachment processAttachment : processAttachments) {
                            List<ProcessAttachment> processAttachmentsByTaskId = taskIdProcessAttachmentsMap.getOrDefault(processAttachment.getTaskId(), new ArrayList<>());
                            processAttachmentsByTaskId.add(processAttachment);
                            taskIdProcessAttachmentsMap.put(processAttachment.getTaskId(), processAttachmentsByTaskId);
                            fileKeys.add(processAttachment.getDescription());
                        }
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(ids)) {
                        List<ProcessComment> processComments = processCommentRepository.getProcessCommentList(ProcessCommentQuery
                                .builder()
                                .taskIds(new ArrayList<>(ids))
                                .build());
                        for (ProcessComment processComment : processComments) {
                            List<ProcessComment> processCommentsByTaskId = taskIdProcessCommentsMap.getOrDefault(processComment.getTaskId(), new ArrayList<>());
                            processCommentsByTaskId.add(processComment);
                            taskIdProcessCommentsMap.put(processComment.getTaskId(), processCommentsByTaskId);
                        }
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(ids)) {
                        List<ProcessVariable> processVariables = processVariableRepository.getProcessVariableList(ProcessVariableQuery
                                .builder()
                                .taskIds(new ArrayList<>(ids))
                                .build());
                        for (ProcessVariable processVariable : processVariables) {
                            List<ProcessVariable> processVariablesByTaskId = taskIdProcessVariablesMap.getOrDefault(processVariable.getTaskId(), new ArrayList<>());
                            processVariablesByTaskId.add(processVariable);
                            taskIdProcessVariablesMap.put(processVariable.getTaskId(), processVariablesByTaskId);
                        }
                    }
                }, IO_INSTANCE)
        ).join();

        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(orgIds)) {
                        List<OrganizationResp> organizations = organizationRemoteApiService.getOrganizationList(OrganizationQuery
                                .builder()
                                .ids(new ArrayList<>(orgIds))
                                .build());
                        for (OrganizationResp organization : organizations) {
                            idOrgMap.put(organization.getId(), appConverter.organizationRespToOrganization(organization));
                        }
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(fileKeys)) {
                        List<FileInfoResponse> files = fileRemoteApi.getFileList(AttachmentQuery
                                .builder()
                                .signKeys(new ArrayList<>(fileKeys))
                                .build());
                        for (FileInfoResponse fileInfoResponse : files) {
                            com.smedi.ismedi.inmail.core.domain.aggregatemodel.Attachment attachment = appConverter.fileInfoResponseToAttachment(fileInfoResponse);
                            keyFileMap.put(attachment.getSignKey(), attachment);
                        }
                    }
                }, IO_INSTANCE)
        ).join();

        for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
            ProcessTask processTask = appConverter.historicTaskInstanceToProcessTask(historicTaskInstance);

            String id = processTask.getId();

            String ownerIdString = historicTaskInstance.getOwner();
            if (null != ownerIdString) {
                Long personId = Long.valueOf(ownerIdString);
                Person owner = idPersonMap.getOrDefault(personId, new Person(personId));
                Long orgId = personIdOrgIdMap.get(personId);
                if (VerifyHelper.isAvailable(orgId)) {
                    owner.setOrganization(idOrgMap.getOrDefault(orgId, new Organization(orgId)));
                }
                processTask.setOwner(idPersonMap.getOrDefault(personId, new Person(personId)));
            }

            processTask.setComments(taskIdProcessCommentsMap.getOrDefault(id, new ArrayList<>()));

            List<Attachment> fileByTaskId = new ArrayList<>();
            List<ProcessAttachment> attachmentsByTaskId = taskIdProcessAttachmentsMap.getOrDefault(id, new ArrayList<>());
            for (ProcessAttachment attachment : attachmentsByTaskId) {
                fileByTaskId.add(keyFileMap.getOrDefault(attachment.getDescription(), new Attachment(attachment.getDescription())));
            }
            processTask.setFiles(fileByTaskId);

            List<ProcessVariable> processVariablesByTaskId = taskIdProcessVariablesMap.getOrDefault(id, new ArrayList<>());
            Map<String, String> variables = new HashMap<>();
            for (ProcessVariable variable : processVariablesByTaskId) {
                variables.put(variable.getName(), variable.getText());
            }
            processTask.setVariables(variables);

            // 为了兼容之前的业务逻辑且同时兼容大小写  单独保存METHOD变量
            ProcessVariable method = processVariablesByTaskId.stream().filter(processVariable -> processVariable.getName().equals(ConstantField.METHOD)||processVariable.getName().equals(ConstantField.method)).findAny().orElse(null);
            if (VerifyHelper.isAvailable(method)) {
                processTask.setMethod(method.getText());
            }

            processTasks.add(processTask);
        }

        return processTasks;
    }
}
